「我的 RAG 看起來答得不錯。」
這句話最大的問題不是「不錯」太主觀,而是我們根本不知道它在做哪一種任務。
使用者輸入「VPN 登入失敗怎麼處理」,系統可能需要找出一份故障排除文件;也可能要定位文件裡真正能支持答案的段落;最後才可能由 LLM 整理成步驟。這三件事分別是文件檢索、證據檢索與答案生成,成功條件並不相同。
如果任務沒有先定義,換 embedding、調 chunk size、加 reranker,最後只會得到更多看似精準、其實無法比較的 Demo。
所以 Day 02 先做一件看起來不夠「AI」、卻決定後續 28 天是否有意義的工作:把 query、corpus、relevance 與 metric 寫成一份可執行的 retrieval task。
這裡只定義評測契約,不提前做後面的實驗:Day 03 會清理 corpus,Day 04 擴充 query set,Day 05 才深入建立 qrels,Day 06 檢查標註一致性,Day 07 定義 metric,Day 08 才正式跑 BM25 baseline。今天只做資料契約檢查,沒有 retrieval ranking,也不會報 BM25 分數。
一個可以評測的企業知識檢索任務,至少要說清楚四件事。
不是只收集幾句範例,而是建立 query taxonomy。以內部知識搜尋為例,可以先分成:
| Query 類型 | 範例 | 主要難點 |
|---|---|---|
| 故障排除 | VPN 登入失敗怎麼處理? | 問法口語、答案可能跨文件 |
| 規則查詢 | 八人以上會議室要核准嗎? | 數字、條件與例外必須精準 |
| 操作程序 | 更換手機後如何設定 MFA? | 步驟順序與身分驗證 |
| 責任查詢 | 服務中斷時要通知誰? | 角色與 escalation 條件 |
| 時效資訊 | 測試資料保存多久? | 版本與有效日期 |
分類的目的不是把問題貼標籤,而是避免測試集全是關鍵字明確的簡單題。之後每次比較都要看 per-query 或 per-type delta,否則總平均可能掩蓋某一類問題大幅退步。
候選單位可以是整份文件、段落、chunk、表格列或 FAQ。這次先用「短文件」做 retrieval unit,原因是要先驗證評測管線,不把 chunking 同時混進來。
這也是實驗控制:Day 02 固定 document-level retrieval;後面調 chunking 時,才知道差異真的來自切分策略,而不是資料集與 metric 一起變動。
只出現相同關鍵字,不代表文件能支持答案。這次使用三級標註:
0 = 不相關,不能支持回答
1 = 部分相關,提供必要的補充或次要證據
2 = 高度相關,可直接支持主要答案
例如「VPN 登入失敗怎麼處理」的主要文件是 VPN 故障排除,因此標為 2;MFA 文件可能是登入失敗時需要的補充資訊,因此標為 1。這個差異會反映在 NDCG,而不只是「有命中就算成功」。
NIST TREC 將 relevance judgment 視為 test collection 的「right answers」,並提醒 corpus 與 qrels 必須匹配。也就是說,qrels 不是可以跨版本沿用的裝飾品;文件集合一變,評測基準也要重新確認。
如果 UI 只顯示三個來源,Hit@10 再好也沒有直接意義。本系列先固定觀察 K = 1, 3, 5:
Hit@K:Top-K 是否至少出現一份 relevant 文件。Recall@K:所有 relevant 文件中,Top-K 找回多少。MRR:第一份 relevant 文件出現得多前面。NDCG@K:高度相關文件是否排在前面。Precision@K:Top-K 中有多少真的是 relevant。這些指標不會互相取代。對 RAG 而言,Recall 通常很重要;但 context window 有成本,把大量無關文件一起塞給 LLM 也會造成 noise,因此 Precision 與排序仍然需要看。
企業文件通常不能直接公開。我沒有把真實公司內容換幾個名稱就拿來用,而是從零撰寫 7 份 synthetic 文件,涵蓋 IT 支援、共享資源、事故通報、資料治理與 GPU 實驗等情境。
一筆文件長這樣:
{
"doc_id": "synthetic-network-vpn",
"title": "VPN 連線故障排除",
"text": "虛構組織的遠端工作者若 VPN 登入失敗,先確認網路連線、帳號狀態與多因素驗證是否完成。連續失敗時建立支援案件,不要重複猜測密碼。",
"metadata": {
"domain": "synthetic-it-support",
"language": "zh-Hant",
"classification": "synthetic/public-safe"
}
}
Query 使用獨立 ID,避免日後改寫文字時失去對應關係:
{"query_id":"q-vpn-login","query":"VPN 登入失敗怎麼處理"}
{"query_id":"q-mfa-device","query":"更換手機後如何設定 MFA"}
qrels 則把 query、document 與 relevance grade 固定下來:
query_id,doc_id,relevance
q-vpn-login,synthetic-network-vpn,2
q-vpn-login,synthetic-identity-mfa,1
q-mfa-device,synthetic-identity-mfa,2
q-mfa-device,synthetic-network-vpn,1
這份資料集很小,不能代表 production;用途是讓資料契約先成立。等 Day 03 到 Day 06 完成 corpus 與標註工作,Day 08 的 baseline 才有合理的輸入。
新增一個只負責資料契約的 validator。檢查:
doc_id 與 query_id 是否唯一且非空。synthetic/public-safe。執行命令:
PYTHONPATH=src python3 labs/retrieval/validate_task.py \
--experiment-id retrieval-task-day02-20260916-001
實際輸出:
{
"status": "valid",
"documents": 7,
"queries": 7,
"qrel_pairs": 9,
"relevance_grades": [1, 2],
"queries_with_multiple_relevant": 2,
"orphan_queries": 0,
"orphan_documents": 0,
"experiment": "results/raw/retrieval-task-day02-20260916-001/experiment.json"
}

圖 1:真實 validator 執行報告,顯示 7 份文件、7 筆 query、9 筆 qrel,orphan query/document 均為 0
relevance_grades 沒有出現 0 並不是錯誤。qrels 通常只列出已判定的正相關文件;未列出的 corpus 文件,在這個最小任務中視為不相關。真正需要警戒的是 orphan query/document 或沒有任何正相關文件的 query,因為那會讓後面的評測失去明確答案。
Validator 同時留下 JSON、CSV、Markdown report 與 SHA-256 manifest。Manifest 的 notes 明確寫著:這次沒有執行 ranking、retrieval、embedding、reranking 或 RAG evaluation。這才是 Day 02 應有的證據邊界。
Day 02 最重要的產物可以濃縮成這份 task card:
| 欄位 | 本次定義 |
|---|---|
| 使用情境 | 繁體中文企業知識搜尋 |
| Retrieval unit | 短文件 |
| Corpus | 7 份 synthetic/public-safe 文件 |
| Query set | 7 題,涵蓋故障、規則、程序、責任與時效 |
| Relevance | 0 不相關、1 部分相關、2 高度相關 |
| 本次驗證 | ID 唯一性、引用完整性、正相關覆蓋、公開分類 |
| Baseline | 尚未執行;保留到 Day 08 |
| 尚未涵蓋 | Corpus 清理、正式標註、Metric 實作、Ranking、Generation |
從下一篇開始,corpus hygiene、chunking、embedding、hybrid search、fine-tuning 與 rerank 都要遵守同一個原則:固定測試資料與 qrels,一次只讓一個主要變因進場,並保留退步的 query。
RAG 工程不是從 Vector Database 開始,而是從一句可被檢驗的任務定義開始:
對固定的 query 與 corpus,系統要在前 K 個結果中找回哪些證據,相關程度如何定義,我們用什麼 metric 判定它真的變好?
這篇沒有產生 BM25 分數;產出的是一份通過檢查、可以被後續實驗共同使用的 task contract。Day 03 會處理 corpus hygiene:重複文件、過期內容、錯誤 metadata 與解析失敗,如何在 embedding 之前就把檢索品質拖垮。